/* 
 *******************************************************************************
 *
 * Copyright (c) 2011, Corey Schram <http://coreymedia.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Core.js nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *******************************************************************************
 *
 * Title: JavaScript Architecture Core <http://code.google.com/p/js-arch-core/>
 * Version: 1.0 alpha
 * Author: Corey Schram
 * 
 * Description: JavaScript Architecture Core.
 * This Application Architecture, built around jQuery, is designed to make
 * developing scalable, modular JavaScript applications easier and quicker.
 *
 * Created: 12/24/2010
 * Modified: 02/6/2010
 *
 * TODO:
 * - Large scale testing to catch bugs and test efficiency.
 * 
 ******************************************************************************
 */

var Core = (function ($, console) {
    "use strict";
    var initialized = false,
        moduleTypes = {},
        modules = [],
        plugins = [],
        body = $('body'),
        ops = {},
        moduleBase;
    
    function ModuleExistsException(selector) {
        this.message = 'Module ' + selector + ' already exists.';
        this.name = 'ModuleExistsException';
    }
    ModuleExistsException.prototype.toString = function () {
        return this.name + ': "' + this.message + '"';
    };
    
    function inArray(val, arr) {
        var i;
        for (i = 0; i < arr.length; i++) {
            if (arr[i] === val) {
                return true;
            }
        }
        return false;
    }
    
    // Base functionality given to every module instance
    moduleBase = {
        // Log (generally to the console)
        log : function (msg) {
            if (console.log) {
                console.log(msg);
            }
        },
        /*
         * 'destroy' method to remove a module
         * Note that this method does not destroy the associated element.
         * That can be done in the 'destroy' method (deconstructor) with
         * 'this.root.detach()'
         */
        del : function () {
            var id = this.context.data('module-index'),
                i;
            console.log('Destroying Module ' + this.name);
            if (this.events.destroy) {
                this.context.trigger('destroy');
            }
            modules.splice(id, 1);
            for (i = id + 1; i < modules.length; i++) {
                modules[i].context.data('module-index', modules[i].context.data('module-index')-1);
            }
        },
        /*
         * Send a signal with a named type and associated data to any
         * module listening for that signal type.
         */
        send : function () {
            var i;
            for (i = 0; i < modules.length; i++) {
                if (modules[i].events && modules[i].events[arguments[0]]) {
                    modules[i].context.trigger.apply(modules[i].context, arguments);
                }
            }
        }
    };
        
    // Bind Event Hanlder function
    function bindEventHandler(reciever, func) {
        var r = reciever, f = func;
        return function () {
            f.apply(r, arguments);
        };
    }
    
    // Add a module
    function addModule(mod) {
        var i, event;
        // Bind events
        if (mod.events) {
            for (event in mod.events) {
                if (mod.events.hasOwnProperty(event)) {
                    mod.context.bind(event, bindEventHandler(mod, mod.events[event]));
                }
            }
        }
        // Feed the module to plugins with a 'newModule' method
        for (i = 0; i < plugins.length; i++) {
            if (plugins[i].newModule) {
                plugins[i].newModule(selector, mod);
            }
        }
        // Initialize the module if the Core has already been initialized
        if (initialized & mod.init) {
            mod.init(ops);
        }
        // Add the module to the array
        mod.context.data('module-index', modules.length);
        modules[modules.length] = mod;
    }
    
    return {
        /*
         * Create a module for every element matching a selector
         * Arguments: Selector string, Module template object
         */
        createModule : function (selector, module) {
            var mod;
            if (!moduleTypes.hasOwnProperty(selector)) {
                moduleTypes[selector] = $.extend({}, moduleBase, module);
                $(selector).each(function (index) {
                    mod = $.extend({}, moduleTypes[selector]);
                    mod.context = $(this);
                    addModule(mod);
                });
            } else {
                throw new ModuleExistsException(selector);
            }
        },
        /*
         * Create a module based on another existing module type
         * Arguments: Base module type, selector string, module template object
         */
        createModuleFrom : function (base, selector, module) {
            var subMod, mod;
            if (!moduleTypes.hasOwnProperty(selector)) {
                subMod = $.extend(true, {}, moduleTypes[base], module);
                subMod._super = moduleTypes[base];
                moduleTypes[selector] = subMod;
                $(selector).each(function (index) {
                    mod = $.extend({}, subMod);
                    mod.context = $(this);
                    addModule(mod);
                });
            } else {
                throw new ModuleExistsException(selector);
            }
        },
        /*
         * Create a Plugin
         * Arguments: Plugin object
         * Description: Plugin objects can have two optional methods, 'init' and 'addModule.'
         * These are called by the core when it is initialized, and when a module is created,
         * respectively.
         * Plugins are also given a method 'find' which takes a selector and will return
         * all plugins whose root elements that match the selector.
         */
        createPlugin : function (plugin) {
            plugins[plugins.length] = plugin;
            plugin.send = moduleBase.send;
            plugin.find = function (selector) {
                var results = [], i;
                for (i = 0; i < modules.length; i++) {
                    if (modules[i].rootNode.selector === selector
                        || modules[i].rootNode.is(selector)) {
                        results[results.length] = modules[i];
                    }
                }
                return results;
            };
        },
        /*
         * Initialize the Core
         * Arguments: Options object
         * Description: Initializes modules and plugins, passing the options
         * object to them.
         */
        init : function (options) {
            var i;
            ops = options | {};
            initialized = true;
            for (i = 0; i < modules.length; i++) {
                if (modules[i].init) {
                    modules[i].init(ops);
                }
            }
            for (i = 0; i < plugins.length; i++) {
                if (plugins[i].init) {
                    plugins[i].init(ops, modules);
                }
            }
        }
    };
}(jQuery, console));